package com.jtexplorer.service.impl;

import com.jtexplorer.plc.OpcNodeList;
import com.jtexplorer.service.OPCUAService;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.client.UaTcpStackClient;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

@Service
public class OPCUAServiceImpl implements OPCUAService {

    public OpcUaClient opcUaClient;
    private String endpointUrl = "opc.tcp://192.168.43.210:49320";

    @Override
    public OpcUaClient createClient() {
        if(opcUaClient != null){
            return opcUaClient;
        }

        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
        try {
            Files.createDirectories(securityTempDir);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (!Files.exists(securityTempDir)) {
            try {
                throw new Exception("不能够创建安全路径: " + securityTempDir);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
//        KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
        // 获取OPC UA的服务器端节点，OPCUA有多种加密方法（可在PLC中设置），当前只列出了不使用任何加密的方式进行连接，其他方式的连接会在以后进行补充
        //applicationUri 可在PLC中查询到，applicationName为uri冒号后面的内容；
        EndpointDescription[] endpoints =
                new EndpointDescription[0];
        try {
            endpoints = UaTcpStackClient.getEndpoints(endpointUrl).get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        EndpointDescription endpoint = null;
        try {
            endpoint = Arrays.stream(endpoints)
                    .filter(e -> e.getEndpointUrl().equals(endpointUrl))
                    .findFirst().orElseThrow(() -> new Exception("没有节点返回"));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        // 设置OPC UA的配置信息
        OpcUaClientConfig config =
                OpcUaClientConfig.builder()
//                        .setApplicationName(LocalizedText.english("PLC_NAME"))
                        .setApplicationName(LocalizedText.english("KEPServerEX/UA Server")) //此处填Uri中冒号后面的字符串
//                        .setApplicationUri("urn:SIMATIC.S7-1500.OPC-UA.Application:PLC_APPNAME")
                        .setApplicationUri("urn:wang-PC:KEPServerEX/UA Server") //PLC连接信息
//                        .setCertificate(loader.getClientCertificate())
//                        .setKeyPair(loader.getClientKeyPair())
                        .setEndpoint(endpoint)
                        .setIdentityProvider(new AnonymousProvider())
                        .setRequestTimeout(uint(5000))
                        .build();
        // 创建OPC UA客户端
        opcUaClient = new OpcUaClient(config);
        return opcUaClient;
    }

    @Override
    public void createSubscription() {
        OpcUaClient client = null;
        try {
            client = createClient();
        } catch (Exception e) {
            e.printStackTrace();
        }
        //创建连接
        try {
            client.connect().get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //创建发布间隔1000ms的订阅对象
        UaSubscription subscription = null;
        try {
            subscription = client.getSubscriptionManager().createSubscription(1000.0).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        //创建监控的参数
        MonitoringParameters parameters1 = new MonitoringParameters(
                uint(1),
                1000.0,     // sampling interval
                null,       // filter, null means use default
                uint(10),   // queue size
                true        // discard oldest
        );


        //创建订阅
        NodeId nodeId1 = new NodeId(2, OpcNodeList.LINE_CYLINDER_SCAN);
        ReadValueId readValueId1 = new ReadValueId(nodeId1, AttributeId.Value.uid(),null,null);
        //该请求最后用于创建订阅。
        MonitoredItemCreateRequest request1 = new MonitoredItemCreateRequest(readValueId1, MonitoringMode.Reporting, parameters1);

        List<MonitoredItemCreateRequest> requests = new ArrayList<>();
        requests.add(request1);

        //创建监控项，并且注册变量值改变时候的回调函数。
        try {
            List<UaMonitoredItem> items = subscription.createMonitoredItems(
                    TimestampsToReturn.Both,
                    requests,
                    (item,id)->{
                        item.setValueConsumer((itemC, value)->{
                            System.out.println("id :"+id);
                            System.out.println("nodeid :"+itemC.getReadValueId().getNodeId());
                            System.out.println("value :"+value.getValue().getValue());
                            //todo 此处监听回调的地址以及数据


                        });
                    }
            ).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }


    /**
     * 项目启动时自动创建 PLC连接并订阅节点
     */
    @PostConstruct
    public void initPlc() {

        try {
            createClient();
            createSubscription();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
